'''
copyright: Copyright (C) 2015-2024, Wazuh Inc.

           Created by Wazuh, Inc. <info@wazuh.com>.

           This program is free software; you can redistribute it and/or modify it under the terms of GPLv2

type: integration

brief: Wazuh is able to detect vulnerabilities in the applications installed in agents using the Vulnerability Detector
       module. This software audit is performed through the integration of vulnerability feeds indexed by Redhat,
       Canonical, Debian, Amazon Linux and NVD Database.

components:
    - vulnerability_detector

suite: scan_types

targets:
    - manager

daemons:
    - wazuh-modulesd

os_platform:
    - linux

os_version:
    - Arch Linux
    - Amazon Linux 2
    - Amazon Linux 1
    - CentOS 8
    - CentOS 7
    - Debian Buster
    - Red Hat 8
    - Ubuntu Focal
    - Ubuntu Bionic

references:
    - https://documentation.wazuh.com/current/user-manual/capabilities/vulnerability-detection/index.html

tags:
    - vulnerability_detector
    - scan_types
'''
import pytest
from pathlib import Path

from wazuh_testing.constants.daemons import ANALYSISD_DAEMON, MODULES_DAEMON, SYSCHECK_DAEMON
from wazuh_testing.utils.configuration import (get_test_cases_data, load_configuration_template,
                                               update_configuration_template)
from wazuh_testing.modules.modulesd.vulnerability_detector import patterns as cb
from wazuh_testing.modules.modulesd.configuration import MODULESD_DEBUG
from wazuh_testing.modules.monitord.configuration import MONITORD_ROTATE_LOG
from wazuh_testing.utils.db_queries import agent_db, cve_db
from wazuh_testing.utils.file import truncate_file
from wazuh_testing.utils.time import get_current_timestamp
from wazuh_testing.constants.paths.logs import ALERTS_JSON_PATH
from wazuh_testing.utils.mocking import VULNERABLE_PACKAGES
from test_vulnerability_detector import utils as evm
from . import (TEST_CASES_PATH, CONFIGURATIONS_PATH, custom_rhel_oval_feed_path, custom_rhel_json_feed_path,
               custom_nvd_json_feed_path)


pytest.skip("The tests will be deprecated, they test the old Vulnerability Detector.", allow_module_level=True)

# Variables
local_internal_options = {MODULESD_DEBUG: '2', MONITORD_ROTATE_LOG: '0'}
daemons_handler_configuration = {'daemons': [ANALYSISD_DAEMON, MODULES_DAEMON, SYSCHECK_DAEMON]}
pytestmark = [pytest.mark.server]

# Configuration and cases data
configurations_path = Path(CONFIGURATIONS_PATH, 'configuration_partial_scan.yaml')
t1_cases_path = Path(TEST_CASES_PATH, 'cases_partial_scan_start.yaml')
t2_cases_path = Path(TEST_CASES_PATH, 'cases_partial_scan_no_alert_after_no_changes.yaml')
t3_cases_path = Path(TEST_CASES_PATH, 'cases_partial_scan_alert_after_changes.yaml')
t4_cases_path = Path(TEST_CASES_PATH, 'cases_partial_scan_remove_vulnerability_alert.yaml')


# test_partial_scan_start configurations
t1_configuration_parameters, t1_configuration_metadata, t1_case_ids = get_test_cases_data(t1_cases_path)
t1_configurations = load_configuration_template(configurations_path, t1_configuration_parameters,
                                                t1_configuration_metadata)
t1_systems = [metadata['system'] for metadata in t1_configuration_metadata]

# test_partial_scan_no_alert_after_no_changes configurations
t2_configuration_parameters, t2_configuration_metadata, t2_case_ids = get_test_cases_data(t2_cases_path)
t2_configurations = load_configuration_template(configurations_path, t2_configuration_parameters,
                                                t2_configuration_metadata)
t2_systems = [metadata['system'] for metadata in t2_configuration_metadata]

# test_partial_scan_alert_after_changes configurations
t3_configuration_parameters, t3_configuration_metadata, t3_case_ids = get_test_cases_data(t3_cases_path)
t3_configurations = load_configuration_template(configurations_path, t3_configuration_parameters,
                                                t3_configuration_metadata)
t3_systems = [metadata['system'] for metadata in t3_configuration_metadata]

# test_partial_scan_remove_vulnerability_alert configurations
t4_configuration_parameters, t4_configuration_metadata, t4_case_ids = get_test_cases_data(t4_cases_path)
t4_configurations = load_configuration_template(configurations_path, t4_configuration_parameters,
                                                t4_configuration_metadata)
t4_systems = [metadata['system'] for metadata in t4_configuration_metadata]

# Set offline custom feeds configuration
t1_configurations = update_configuration_template(
    t1_configurations,  ['CUSTOM_REDHAT_OVAL_FEED', 'CUSTOM_REDHAT_JSON_FEED', 'CUSTOM_NVD_JSON_FEED'],
    [custom_rhel_oval_feed_path, custom_rhel_json_feed_path, custom_nvd_json_feed_path])
t2_configurations = update_configuration_template(
    t2_configurations,  ['CUSTOM_REDHAT_OVAL_FEED', 'CUSTOM_REDHAT_JSON_FEED', 'CUSTOM_NVD_JSON_FEED'],
    [custom_rhel_oval_feed_path, custom_rhel_json_feed_path, custom_nvd_json_feed_path])
t3_configurations = update_configuration_template(
    t3_configurations,  ['CUSTOM_REDHAT_OVAL_FEED', 'CUSTOM_REDHAT_JSON_FEED', 'CUSTOM_NVD_JSON_FEED'],
    [custom_rhel_oval_feed_path, custom_rhel_json_feed_path, custom_nvd_json_feed_path])
t4_configurations = update_configuration_template(
    t4_configurations,  ['CUSTOM_REDHAT_OVAL_FEED', 'CUSTOM_REDHAT_JSON_FEED', 'CUSTOM_NVD_JSON_FEED'],
    [custom_rhel_oval_feed_path, custom_rhel_json_feed_path, custom_nvd_json_feed_path])


@pytest.mark.tier(level=0)
@pytest.mark.parametrize('test_configuration, test_metadata, agent_system',
                         zip(t1_configurations, t1_configuration_metadata, t1_systems), ids=t1_case_ids)
def test_partial_scan_start(test_configuration, test_metadata, agent_system, set_wazuh_configuration,
                            configure_local_internal_options, truncate_monitored_files, clean_cve_tables,
                            prepare_partial_scan_with_vuln_packages, daemons_handler):
    '''
    description: Check that the partial scan starts.

    test_phases:
        - Setup:
            - Set a custom Wazuh configuration, with custom feeds for OVAL and NVD.
            - Mock an agent with a vulnerable packages.
            - Update sync_info packages data for that mocked agent.
            - Force a partial scan setting the last full scan DB data.
            - Truncate log files
            - Restart wazuh-modulesd.
        - Test:
            - Check in log that the partial scan starts.
        - Teardown:
            - Truncate log files
            - Clean CVE tables
            - Remove mocked agent
            - Restore configuration files
            - Restart wazuh-modulesd

    wazuh_min_version: 4.3.0

    tier: 1

    parameters:
        - test_configuration:
            type: dict
            brief: Wazuh configuration data. Needed for set_wazuh_configuration fixture.
        - test_metadata:
            type: dict
            brief: Wazuh configuration metadata
        - agent_system:
            type: str
            brief: System to set to the mocked agent.
        - set_wazuh_configuration:
            type: fixture
            brief: Set the wazuh configuration according to the configuration data.
        - configure_local_internal_options:
            type: fixture
            brief: Set local_internal_options configuration.
        - truncate_monitored_files:
            type: fixture
            brief: Truncate all the log files and json alerts files before and after the test execution.
        - clean_cve_tables:
            type: fixture
            brief: Clean all CVE tables.
        - prepare_partial_scan_with_vuln_packages:
            type: fixture
            brief: Setup the initial test state.
        - daemons_handler:
            type: fixture
            brief: Restart the wazuh-modulesd daemon.

    assertions:
        - Check that the partial scan log appears.

    input_description:
        - The `configuration_partial_scan.yaml` file provides the module configuration for this test.
        - The `cases_partial_scan_start` file provides the test cases.

    expected_output:
        - f"A partial scan will be run on agent '{agent_id}'"
    '''
    agent_id = prepare_partial_scan_with_vuln_packages

    # Check (in log) that the partial scan has been launched
    log_present = evm.check_partial_scan_start(agent_id=agent_id)
    assert log_present is not None, f"No Partial scan start log found."


@pytest.mark.tier(level=1)
@pytest.mark.parametrize('test_configuration, test_metadata, agent_system',
                         zip(t2_configurations, t2_configuration_metadata, t2_systems), ids=t2_case_ids)
def test_partial_scan_no_alert_after_no_changes(test_configuration, test_metadata, agent_system,
                                                set_wazuh_configuration, configure_local_internal_options,
                                                truncate_monitored_files, clean_cve_tables,
                                                prepare_full_scan_with_vuln_package, daemons_handler):
    '''
    description: Check that the partial scan does not report previously reported vulnerabilities.

    test_phases:
        - Setup:
            - Set a custom Wazuh configuration, with custom feeds for OVAL and NVD.
            - Mock an agent with a vulnerable packages.
            - Update sync_info packages data for that mocked agent.
            - Force a full scan setting the last full scan DB data.
            - Truncate log files
            - Restart wazuh-modulesd.
        - Test:
            - Check that the package vulnerability has been reported in the full scan.
            - Wait for the partial scan.
            - Check that the package vulnerability previously reported has not been reported again.
        - Teardown:
            - Truncate log files
            - Clean CVE tables
            - Remove mocked agent
            - Restore configuration files
            - Restart wazuh-modulesd

    wazuh_min_version: 4.3.0

    tier: 1

    parameters:
        - test_configuration:
            type: dict
            brief: Wazuh configuration data. Needed for set_wazuh_configuration fixture.
        - test_metadata:
            type: dict
            brief: Wazuh configuration metadata
        - agent_system:
            type: str
            brief: System to set to the mocked agent.
        - set_wazuh_configuration:
            type: fixture
            brief: Set the wazuh configuration according to the configuration data.
        - configure_local_internal_options:
            type: fixture
            brief: Set local_internal_options configuration.
        - truncate_monitored_files:
            type: fixture
            brief: Truncate all the log files and json alerts files before and after the test execution.
        - clean_cve_tables:
            type: fixture
            brief: Clean all CVE tables.
        - prepare_full_scan_with_vuln_package:
            type: fixture
            brief: Setup the initial test state.
        - daemons_handler:
            type: fixture
            brief: Restart the wazuh-modulesd daemon.

    assertions:
        - Check that the package vulnerability is reported the first time.
        - Check that the package vulnerability is not reported in the next partial scan.

    input_description:
        - The `configuration_partial_scan.yaml` file provides the module configuration for this test.
        - The `cases_partial_scan_no_alert_after_no_changes` file provides the test cases.

    expected_output:
        - f".*"agent":."id":"{agent_id}".*{cve} affects {package}"
        - f"NOT .*"agent":."id":"{agent_id}".*{cve} affects {package}"
    '''
    agent_id = prepare_full_scan_with_vuln_package
    cve = VULNERABLE_PACKAGES[0]['cveid']
    package = VULNERABLE_PACKAGES[0]['name']

    # Check that the alert appears
    log_present = evm.check_cve_affects_package_alert(agent_id, package, cve, timeout=20)
    assert log_present is not None, f"Expected '{cve} affects {package}' alert not found"

    # Force again a partial scan, updating the last full scan timestamp and simulating the NVD feed update
    agent_db.update_last_full_scan(1, agent_id=agent_id)
    cve_db.update_nvd_metadata_vuldet(1)
    truncate_file(ALERTS_JSON_PATH)

    # Check for the next partial scan
    log_present = evm.check_partial_scan_start(agent_id=agent_id)
    assert log_present is not None, f"No Partial scan start log found."

    # Check that there is no alert (since it was generated in the last scan)
    log_present = evm.check_cve_affects_package_alert(agent_id, package, cve)
    assert log_present is None, f"Vulnerability alert was generated again after being reported in the last scan"


@pytest.mark.tier(level=1)
@pytest.mark.parametrize('test_configuration, test_metadata, agent_system',
                         zip(t3_configurations, t3_configuration_metadata, t3_systems), ids=t3_case_ids)
def test_partial_scan_alert_after_changes(test_configuration, test_metadata, agent_system,
                                          set_wazuh_configuration, configure_local_internal_options,
                                          truncate_monitored_files, clean_cve_tables,
                                          prepare_full_scan_with_vuln_package, daemons_handler):
    '''
    description: Check that the partial scan reports a new vulnerability from the latest scan.

    test_phases:
        - Setup:
            - Set a custom Wazuh configuration, with custom feeds for OVAL and NVD.
            - Mock an agent with a vulnerable packages.
            - Update sync_info packages data for that mocked agent.
            - Force a full scan setting the last full scan DB data.
            - Truncate log files
            - Restart wazuh-modulesd.
        - Test:
            - Wait for the partial scan.
            - Insert a new vulnerable package.
            - Check that there is a new alert reporting the new vulnerability.
        - Teardown:
            - Truncate log files
            - Clean CVE tables
            - Remove mocked agent
            - Restore configuration files
            - Restart wazuh-modulesd

    wazuh_min_version: 4.3.0

    tier: 1

    parameters:
        - test_configuration:
            type: dict
            brief: Wazuh configuration data. Needed for set_wazuh_configuration fixture.
        - test_metadata:
            type: dict
            brief: Wazuh configuration metadata
        - agent_system:
            type: str
            brief: System to set to the mocked agent.
        - set_wazuh_configuration:
            type: fixture
            brief: Set the wazuh configuration according to the configuration data.
        - configure_local_internal_options:
            type: fixture
            brief: Set local_internal_options configuration.
        - truncate_monitored_files:
            type: fixture
            brief: Truncate all the log files and json alerts files before and after the test execution.
        - clean_cve_tables:
            type: fixture
            brief: Clean all CVE tables.
        - prepare_full_scan_with_vuln_package:
            type: fixture
            brief: Setup the initial test state.
        - daemons_handler:
            type: fixture
            brief: Restart the wazuh-modulesd daemon.

    assertions:
        - Check that the new vulnerability has been reported as alert.

    input_description:
        - The `configuration_partial_scan.yaml` file provides the module configuration for this test.
        - The `cases_partial_scan_alert_after_changes` file provides the test cases.

    expected_output:
        - f".*"agent":."id":"{agent_id}".*{cve} affects {package}"
    '''
    agent_id = prepare_full_scan_with_vuln_package

    # Check for the next partial scan
    log_present = evm.check_partial_scan_start(agent_id=agent_id)
    assert log_present is not None, f"No Partial scan start log found."

    # Insert a new vulnerable package
    agent_db.insert_package(name=VULNERABLE_PACKAGES[1]['name'], version=VULNERABLE_PACKAGES[1]['version'],
                            source=VULNERABLE_PACKAGES[1]['name'], agent_id=agent_id, vendor='Red Hat, Inc.')

    # Force again a partial scan, updating the last full scan timestamp and simulating the NVD feed update
    agent_db.update_last_full_scan(1, agent_id=agent_id)
    cve_db.update_nvd_metadata_vuldet(1)

    # Check for the next partial scan
    log_present = evm.check_partial_scan_start(agent_id=agent_id)
    assert log_present is not None, f"No Full scan start log found."

    # Check that the alert appears
    log_present = evm.check_cve_affects_package_alert(agent_id, VULNERABLE_PACKAGES[1]['name'],
                                                      VULNERABLE_PACKAGES[1]['cveid'])
    assert log_present is not None, f"Expected '{cve} affects {package}' alert not found"


@pytest.mark.tier(level=1)
@pytest.mark.parametrize('test_configuration, test_metadata, agent_system',
                         zip(t4_configurations, t4_configuration_metadata, t4_systems), ids=t4_case_ids)
def test_partial_scan_remove_vulnerability_alert(test_configuration, test_metadata, agent_system,
                                                 set_wazuh_configuration, configure_local_internal_options,
                                                 truncate_monitored_files, clean_cve_tables,
                                                 prepare_full_scan_with_vuln_package, daemons_handler):
    '''
    description: Check that the partial scan reports removed vulnerabilities.

    test_phases:
        - Setup:
            - Set a custom Wazuh configuration, with custom feeds for OVAL and NVD.
            - Mock an agent with vulnerable packages.
            - Update sync_info packages data for that mocked agent.
            - Force a full scan setting the last full scan DB data.
            - Truncate log files
            - Restart wazuh-modulesd.
        - Test:
            - Check that the package vulnerability is reported
            - Remove the vulnerable package.
            - Wait for the next partial scan.
            - Check that there is a new alert reporting the removed vulnerability.
        - Teardown:
            - Truncate log files
            - Clean CVE tables
            - Remove mocked agent
            - Restore configuration files
            - Restart wazuh-modulesd

    wazuh_min_version: 4.3.0

    tier: 1

    parameters:
        - test_configuration:
            type: dict
            brief: Wazuh configuration data. Needed for set_wazuh_configuration fixture.
        - test_metadata:
            type: dict
            brief: Wazuh configuration metadata
        - agent_system:
            type: str
            brief: System to set to the mocked agent.
        - set_wazuh_configuration:
            type: fixture
            brief: Set the wazuh configuration according to the configuration data.
        - configure_local_internal_options:
            type: fixture
            brief: Set local_internal_options configuration.
        - truncate_monitored_files:
            type: fixture
            brief: Truncate all the log files and json alerts files before and after the test execution.
        - clean_cve_tables:
            type: fixture
            brief: Clean all CVE tables.
        - prepare_full_scan_with_vuln_package:
            type: fixture
            brief: Setup the initial test state.
        - daemons_handler:
            type: fixture
            brief: Restart the wazuh-modulesd daemon.

    assertions:
        - Check that the removed vulnerability has been reported as alert.

    input_description:
        - The `configuration_partial_scan.yaml` file provides the module configuration for this test.
        - The `cases_partial_scan_remove_vulnerability_alert` file provides the test cases.

    expected_output:
        - f".*"agent":."id":"{agent_id}".*{cve} affecting {package} was solved."
    '''
    agent_id = prepare_full_scan_with_vuln_package
    package = VULNERABLE_PACKAGES[0]['name']
    cve = VULNERABLE_PACKAGES[0]['cveid']

    # Wait for detecting the package vulnerability
    # Check that second vulneratility alert appears
    log_present = evm.check_cve_affects_package_alert(agent_id, package, cve)
    assert log_present is not None, f"Expected '{cve} affects {package}' alert not found"

    # Remove the vulnerable package
    agent_db.delete_package(agent_id=agent_id, package=package)

    # Force again a partial scan, updating the last full scan timestamp and simulating the NVD feed update
    agent_db.update_last_full_scan(1, agent_id=agent_id)
    cve_db.update_nvd_metadata_vuldet(1)

    # Check for the next partial scan
    log_present = evm.check_partial_scan_start(agent_id=agent_id)
    assert log_present is not None, f"No Full scan start log found."

    # Check the removed vulnerability alert
    alert_removed = evm.check_package_vulnerability_solved(cve=cve, package=package)
    assert alert_removed is not None, f"Expected '{cve} affecting {package} was solved.' event not found"
